﻿namespace LiteBlog.Repositories
{
    using System;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Text;
    using System.Web;
    using System.Xml;
    using System.Xml.Linq;

    using LiteBlog.Common;
    using LiteBlog.Common.Components;
    using LiteBlog.Common.Entities;
    using System.Text.RegularExpressions;
    using LiteBlog.Repositories.Contracts;
    using LiteBlog.Repositories.Helpers;
    using System.Collections.Generic;
    using LiteBlog.Common.Enums;
    using LiteBlog.Repositories.Framework;
    using LiteBlog.Repositories.Common;

    // Comments part of PostData should be abstracted away into an internal class: PostCommentData
    public class PostRepository : DataAccess, IPostRepository, IRepository
    {
        private DraftRepository _draftData;

        public PostRepository(IDataContext context, IXmlHelper xmlHelper, DraftRepository draftData)
            : base(context, xmlHelper)
        {
            _draftData = draftData;
        }

        public Post Load(string fileId)
        {
            bool isDraft = DraftPost.IsDraft(fileId);
            var post = isDraft ? new DraftPost() : new Post();
            _path = this.GetFilePath(fileId);
            var postElem = _xmlHelper.Load(_path);

            post.FileID = postElem.Attribute("FileID").Value;
            post.Title = postElem.Attribute("Title").Value;
            post.Author = postElem.Attribute("Author").Value;

            // Fill Categories
            var csvIds = postElem.Attribute("CatID").Value;
            var categoryData = new CategoryRepository(_context, _xmlHelper);
            post.Categories = categoryData.GetCategories(csvIds);

            var tagIdAttr = postElem.Attribute("TagID");
            if (tagIdAttr != null)
            {
                csvIds = tagIdAttr.Value;
                var tagData = new TagRepository(_context, _xmlHelper);
                post.Tags = tagData.GetTags(csvIds);
            }

            post.Time = DateTime.ParseExact(
                postElem.Attribute("Time").Value, Constants.DateTimeFormat, CultureInfo.InvariantCulture);

            var updatedTimeAttr = postElem.Attribute("UpdatedTime");
            if (updatedTimeAttr != null)
                post.UpdatedTime = DateTime.ParseExact(updatedTimeAttr.Value, Constants.DateTimeFormat, CultureInfo.InvariantCulture);
            else
                post.UpdatedTime = post.Time;

            if (isDraft)
            {
                var draftPost = post as DraftPost;
                draftPost.DraftID = fileId;
                string draftType = postElem.Attribute("Type").Value;
                draftPost.Type = (DraftType)Enum.Parse(typeof(DraftType), draftType, true);

                if (draftPost.Type == DraftType.Published)
                {
                    if (postElem.Attribute("OldCatID") != null)
                    {
                        csvIds = postElem.Attribute("OldCatID").Value;
                        draftPost.OldCategories = categoryData.GetCategories(csvIds);
                    }
                }
            }

            XElement docsElem = postElem.Element("Documents");
            if (docsElem != null)
            {
                foreach (XElement docElem in docsElem.Elements("Document"))
                {
                    Document document = new Document();
                    document.VirtualPath = docElem.Attribute("Path").Value;
                    post.Documents.Add(document);
                }
            }

            post.Contents = HttpUtility.HtmlDecode(_xmlHelper.GetInnerXml(postElem.Element("Content")));

            var metaElem = postElem.Element("Meta");
            if (metaElem != null)
                post.Meta = metaElem.Value;

            var postCommentData = new PostCommentRepository(_context, _xmlHelper);
            post.Comments = postCommentData.Get(postElem, post.FileID);

            return post;
        }

        public void Save(Post post)
        {
            var draftPost = post as DraftPost;
            string path = this.GetFilePath(draftPost == null ? post.FileID : draftPost.DraftID);

            string template = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
            template += "<Post Title=\"{0}\" FileID=\"{1}\" Author=\"{2}\" Time=\"{3}\" UpdatedTime=\"{4}\" CatID=\"{5}\" TagID=\"{6}\" Comments=\"{7}\">";
            template += "<Documents></Documents>";
            template += "<Meta></Meta>";
            template += "<Content></Content>";
            template += "<Comments></Comments></Post>";

            string catIds = string.Empty;
            if (post.Categories != null && post.Categories.Count > 0)
                catIds = post.Categories.Select(c => c.CatID).Aggregate((cur, next) => cur + "," + next);

            string tagIds = string.Empty;
            if (post.Tags != null && post.Tags.Count() > 0)
                tagIds = post.Tags.Select(t => t.TagId).Aggregate((cur, next) => cur + "," + next);

            template = string.Format(
                template,
                post.Title,
                post.FileID,
                post.Author,
                post.Time.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture),
                post.UpdatedTime.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture),
                catIds,
                tagIds,
                post.Comments.Count);

            var postElem = XElement.Parse(template);

            var docsElem = postElem.Element("Documents");
            foreach (Document document in post.Documents)
            {
                XElement docElem = new XElement("Document", new XAttribute("Path", document.VirtualPath));
                docsElem.Add(docElem);
            }

            postElem.Element("Content").Value = post.Contents ?? string.Empty;

            postElem.Element("Meta").Value = post.Meta ?? string.Empty;

            var commentsElem = postElem.Element("Comments");
            commentsElem.RemoveAll();

            foreach (Comment comment in post.Comments)
            {
                XElement commentElem = new XElement(
                    "Comment",
                    new XAttribute("ID", comment.ID),
                    new XAttribute("Name", comment.Name),
                    new XAttribute("Url", comment.Url),
                    new XAttribute("IsAuthor", comment.IsAuthor),
                    new XAttribute(
                        "Time", comment.Time.ToString(Constants.DateTimeFormat, CultureInfo.InvariantCulture)),
                    new XAttribute("Ip", comment.Ip));

                commentElem.Value = comment.Text;
                commentsElem.Add(commentElem);
            }

            if (draftPost != null)
            {
                postElem.SetAttributeValue("Type", draftPost.Type.ToString());
                if (draftPost.Type == DraftType.Published)
                {
                    catIds = string.Empty;
                    if (draftPost.OldCategories != null && draftPost.OldCategories.Count > 0)
                        catIds = draftPost.OldCategories.Select(c => c.CatID).Aggregate((cur, next) => cur + "," + next);
                    postElem.SetAttributeValue("OldCatID", catIds);
                }
            }

            _xmlHelper.Save(postElem, path);
        }

        public DraftPost Create(string title)
        {
            var post = new DraftPost();
            post.FileID = string.Empty;
            post.DraftID = DraftPost.GetNewDraftID(this.GetUniqueFileID(title), DateTimeHelper.Now);
            post.Title = title;
            post.Type = DraftType.New;


            string template = "<?xml version=\"1.0\" encoding=\"utf-8\"?>";
            template +=
                @"<Post Title=""{0}"" FileID=""{1}"" Author="""" Time="""" UpdatedTime="""" CatID="""" TagID="""" Comments=""0"" Type=""New"">";
            template += "<Documents></Documents>";
            template += "<Content></Content>";
            template += "<Meta></Meta>";
            template += "<Comments></Comments></Post>";

            string xml = string.Format(template, title, post.FileID);

            XElement postElem = XElement.Parse(xml);

            string path = this.GetFilePath(post.DraftID);
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            XmlWriter writer = XmlWriter.Create(path, settings);
            postElem.Save(writer);
            writer.Close();

            _draftData.Create(post.DraftID, string.Empty, title, new List<Category>());
            return post;
        }

        public void Update(PostInfo postInfo)
        {
            var post = Load(postInfo.FileID);
            post.Title = postInfo.Title;
            post.Categories = postInfo.Categories;
            post.Time = postInfo.Time;
            post.Author = postInfo.Author;
            Save(post);

            var blogData = new BlogRepository(_context, _xmlHelper);
            blogData.Update(postInfo);
        }

        public void Delete(string fileId)
        {
            _path = this.GetFilePath(fileId);
            _xmlHelper.Delete(_path);

            if (DraftPost.IsDraft(fileId))
            {
                _draftData.Delete(fileId);
            }
            else
            {
                var blogData = new BlogRepository(_context, _xmlHelper);
                blogData.Delete(fileId);
            }
        }

        public DraftPost EditPublished(string fileId)
        {
            var post = Load(fileId);
            var draft = new DraftPost(post);
            draft.DraftID = DraftPost.GetNewDraftID(fileId, DateTimeHelper.Now);
            Save(draft);

            _draftData.Create(draft.DraftID, draft.FileID, draft.Title, draft.Categories);
            return draft;
        }

        public string GetDraftIdIfDraftExistsForPublishedPost(string fileId)
        {
            return _draftData.GetDraftIdForPublishedPost(fileId);
        }

        public string Unpublish(string fileId)
        {
            // Create a draft copy of the post
            var post = Load(fileId);
            var draftPost = new DraftPost(post);
            draftPost.DraftID = DraftPost.GetNewDraftID(fileId, DateTimeHelper.Now);
            draftPost.Type = DraftType.Unpublished;
            draftPost.Time = DateTimeHelper.Now;
            Save(draftPost);

            // delete the entry from blog register
            var blogData = new BlogRepository(_context, _xmlHelper);
            blogData.Delete(fileId);

            // insert entry into draft register
            _draftData.Create(draftPost.DraftID, string.Empty, draftPost.Title, draftPost.Categories, draftPost.Time);
            return draftPost.DraftID;
        }


        public string Publish(DraftPost post)
        {
            string filePath = GetFilePath(post.DraftID);
            var root = XElement.Load(filePath);
            root.Attribute("Type").Remove();
            var catAttr = root.Attribute("OldCatID");
            if (catAttr != null)
                catAttr.Remove();

            if (post.Type == DraftType.New)
            {
                post.FileID = GetUniqueFileID(post.Title);
                root.SetAttributeValue("FileID", post.FileID);
            }

            string publishPath = GetFilePath(post.FileID);
            _xmlHelper.Save(root, publishPath);

            // Delete old post
            Delete(post.DraftID);

            if (post.Type == DraftType.Unpublished)
            {
                Refresh(post.FileID);
            }

            // Add an entry into blogdata
            var postInfo = post.AsPostInfo();
            var blogData = new BlogRepository(_context, _xmlHelper);
            if (post.Type != DraftType.Published)
                blogData.Create(postInfo);
            else
            {
                // hack a bit -- modify the entry of the published post
                postInfo.Type = LiteBlog.Common.Enums.PostType.Published;
                blogData.Update(postInfo);
            }

            return post.FileID;
        }

        private void Refresh(string newFileId)
        {
            var post = Load(newFileId);
            var comments = post.Comments;
            comments = comments.OrderByDescending(c => c.Time).ToList();
            var firstComment = comments.FirstOrDefault();
            if (firstComment == null)
                return;
            var oldFileId = firstComment.FileID;

            var root = XElement.Load(_path);
            root.Elements("Comment").Where(elem => (string)elem.Attribute("FileID") == oldFileId).Remove();
            root.Save(_path);

            foreach (var comment in comments)
            {
                comment.FileID = newFileId;
                var commentData = new CommentRepository(_context, _xmlHelper);
                comment.ID = commentData.CreateInternal(comment);
            }

            post.Comments = comments;
            Save(post);
        }

        private string GetUniqueFileID(string title)
        {
            string fileID = Regex.Replace(title.Replace(" ", "-"), @"[^\w-]", string.Empty, RegexOptions.None);
            string path = this.GetFilePath(fileID);

            if (File.Exists(path))
            {
                int index = 1;
                string tmpID = string.Format("{0}-{1}", fileID, index);
                path = this.GetFilePath(tmpID);
                while (File.Exists(path))
                {
                    index++;
                    tmpID = string.Format("{0}-{1}", fileID, index);
                    path = this.GetFilePath(tmpID);
                }

                fileID = tmpID;
            }

            return fileID;
        }

        private string GetFilePath(string fileID)
        {
            return _context.GetPath(DataStoreType.Post, fileID);
        }
    }
}